/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#ifndef PKIM_H#include"pkim.h"#endif /* PKIM_H */#ifndef PKI_H#include"pki.h"#endif /* PKI_H */#ifndef NSSPKI_H#include"nsspki.h"#endif /* NSSPKI_H */#ifndef BASE_H#include"base.h"#endif /* BASE_H */#ifndef PKISTORE_H#include"pkistore.h"#endif /* PKISTORE_H */#include"cert.h"#include"pki3hack.h"#include"prbit.h"/* * Certificate Store * * This differs from the cache in that it is a true storage facility. Items * stay in until they are explicitly removed. It is only used by crypto * contexts at this time, but may be more generally useful... * */structnssCertificateStoreStr{PRBooli_alloced_arena;NSSArena*arena;PZLock*lock;nssHash*subject;nssHash*issuer_and_serial;};typedefstructcertificate_hash_entry_strcertificate_hash_entry;structcertificate_hash_entry_str{NSSCertificate*cert;NSSTrust*trust;nssSMIMEProfile*profile;};/* forward static declarations */staticNSSCertificate*nssCertStore_FindCertByIssuerAndSerialNumberLocked(nssCertificateStore*store,NSSDER*issuer,NSSDER*serial);NSS_IMPLEMENTnssCertificateStore*nssCertificateStore_Create(NSSArena*arenaOpt){NSSArena*arena;nssCertificateStore*store;PRBooli_alloced_arena;if(arenaOpt){arena=arenaOpt;i_alloced_arena=PR_FALSE;}else{arena=nssArena_Create();if(!arena){returnNULL;}i_alloced_arena=PR_TRUE;}store=nss_ZNEW(arena,nssCertificateStore);if(!store){gotoloser;}store->lock=PZ_NewLock(nssILockOther);if(!store->lock){gotoloser;}/* Create the issuer/serial --> {cert, trust, S/MIME profile } hash */store->issuer_and_serial=nssHash_CreateCertificate(arena,0);if(!store->issuer_and_serial){gotoloser;}/* Create the subject DER --> subject list hash */store->subject=nssHash_CreateItem(arena,0);if(!store->subject){gotoloser;}store->arena=arena;store->i_alloced_arena=i_alloced_arena;returnstore;loser:if(store){if(store->lock){PZ_DestroyLock(store->lock);}if(store->issuer_and_serial){nssHash_Destroy(store->issuer_and_serial);}if(store->subject){nssHash_Destroy(store->subject);}}if(i_alloced_arena){nssArena_Destroy(arena);}returnNULL;}externconstNSSErrorNSS_ERROR_BUSY;NSS_IMPLEMENTPRStatusnssCertificateStore_Destroy(nssCertificateStore*store){if(nssHash_Count(store->issuer_and_serial)>0){nss_SetError(NSS_ERROR_BUSY);returnPR_FAILURE;}PZ_DestroyLock(store->lock);nssHash_Destroy(store->issuer_and_serial);nssHash_Destroy(store->subject);if(store->i_alloced_arena){nssArena_Destroy(store->arena);}else{nss_ZFreeIf(store);}returnPR_SUCCESS;}staticPRStatusadd_certificate_entry(nssCertificateStore*store,NSSCertificate*cert){PRStatusnssrv;certificate_hash_entry*entry;entry=nss_ZNEW(cert->object.arena,certificate_hash_entry);if(!entry){returnPR_FAILURE;}entry->cert=cert;nssrv=nssHash_Add(store->issuer_and_serial,cert,entry);if(nssrv!=PR_SUCCESS){nss_ZFreeIf(entry);}returnnssrv;}staticPRStatusadd_subject_entry(nssCertificateStore*store,NSSCertificate*cert){PRStatusnssrv;nssList*subjectList;subjectList=(nssList*)nssHash_Lookup(store->subject,&cert->subject);if(subjectList){/* The subject is already in, add this cert to the list */nssrv=nssList_AddUnique(subjectList,cert);}else{/* Create a new subject list for the subject */subjectList=nssList_Create(NULL,PR_FALSE);if(!subjectList){returnPR_FAILURE;}nssList_SetSortFunction(subjectList,nssCertificate_SubjectListSort);/* Add the cert entry to this list of subjects */nssrv=nssList_Add(subjectList,cert);if(nssrv!=PR_SUCCESS){returnnssrv;}/* Add the subject list to the cache */nssrv=nssHash_Add(store->subject,&cert->subject,subjectList);}returnnssrv;}/* declared below */staticvoidremove_certificate_entry(nssCertificateStore*store,NSSCertificate*cert);/* Caller must hold store->lock */staticPRStatusnssCertificateStore_AddLocked(nssCertificateStore*store,NSSCertificate*cert){PRStatusnssrv=add_certificate_entry(store,cert);if(nssrv==PR_SUCCESS){nssrv=add_subject_entry(store,cert);if(nssrv==PR_FAILURE){remove_certificate_entry(store,cert);}}returnnssrv;}NSS_IMPLEMENTNSSCertificate*nssCertificateStore_FindOrAdd(nssCertificateStore*store,NSSCertificate*c){PRStatusnssrv;NSSCertificate*rvCert=NULL;PZ_Lock(store->lock);rvCert=nssCertStore_FindCertByIssuerAndSerialNumberLocked(store,&c->issuer,&c->serial);if(!rvCert){nssrv=nssCertificateStore_AddLocked(store,c);if(PR_SUCCESS==nssrv){rvCert=nssCertificate_AddRef(c);}}PZ_Unlock(store->lock);returnrvCert;}staticvoidremove_certificate_entry(nssCertificateStore*store,NSSCertificate*cert){certificate_hash_entry*entry;entry=(certificate_hash_entry*)nssHash_Lookup(store->issuer_and_serial,cert);if(entry){nssHash_Remove(store->issuer_and_serial,cert);if(entry->trust){nssTrust_Destroy(entry->trust);}if(entry->profile){nssSMIMEProfile_Destroy(entry->profile);}nss_ZFreeIf(entry);}}staticvoidremove_subject_entry(nssCertificateStore*store,NSSCertificate*cert){nssList*subjectList;/* Get the subject list for the cert's subject */subjectList=(nssList*)nssHash_Lookup(store->subject,&cert->subject);if(subjectList){/* Remove the cert from the subject hash */nssList_Remove(subjectList,cert);nssHash_Remove(store->subject,&cert->subject);if(nssList_Count(subjectList)==0){nssList_Destroy(subjectList);}else{/* The cert being released may have keyed the subject entry. * Since there are still subject certs around, get another and * rekey the entry just in case. */NSSCertificate*subjectCert;(void)nssList_GetArray(subjectList,(void**)&subjectCert,1);nssHash_Add(store->subject,&subjectCert->subject,subjectList);}}}NSS_IMPLEMENTvoidnssCertificateStore_RemoveCertLOCKED(nssCertificateStore*store,NSSCertificate*cert){certificate_hash_entry*entry;entry=(certificate_hash_entry*)nssHash_Lookup(store->issuer_and_serial,cert);if(entry&&entry->cert==cert){remove_certificate_entry(store,cert);remove_subject_entry(store,cert);}}NSS_IMPLEMENTvoidnssCertificateStore_Lock(nssCertificateStore*store,nssCertificateStoreTrace*out){#ifdef DEBUGPORT_Assert(out);out->store=store;out->lock=store->lock;out->locked=PR_TRUE;PZ_Lock(out->lock);#elsePZ_Lock(store->lock);#endif}NSS_IMPLEMENTvoidnssCertificateStore_Unlock(nssCertificateStore*store,constnssCertificateStoreTrace*in,nssCertificateStoreTrace*out){#ifdef DEBUGPORT_Assert(in);PORT_Assert(out);out->store=store;out->lock=store->lock;PORT_Assert(!out->locked);out->unlocked=PR_TRUE;PORT_Assert(in->store==out->store);PORT_Assert(in->lock==out->lock);PORT_Assert(in->locked);PORT_Assert(!in->unlocked);PZ_Unlock(out->lock);#elsePZ_Unlock(store->lock);#endif}staticNSSCertificate**get_array_from_list(nssList*certList,NSSCertificate*rvOpt[],PRUint32maximumOpt,NSSArena*arenaOpt){PRUint32count;NSSCertificate**rvArray=NULL;count=nssList_Count(certList);if(count==0){returnNULL;}if(maximumOpt>0){count=PR_MIN(maximumOpt,count);}if(rvOpt){nssList_GetArray(certList,(void**)rvOpt,count);}else{rvArray=nss_ZNEWARRAY(arenaOpt,NSSCertificate*,count+1);if(rvArray){nssList_GetArray(certList,(void**)rvArray,count);}}returnrvArray;}NSS_IMPLEMENTNSSCertificate**nssCertificateStore_FindCertificatesBySubject(nssCertificateStore*store,NSSDER*subject,NSSCertificate*rvOpt[],PRUint32maximumOpt,NSSArena*arenaOpt){NSSCertificate**rvArray=NULL;nssList*subjectList;PZ_Lock(store->lock);subjectList=(nssList*)nssHash_Lookup(store->subject,subject);if(subjectList){nssCertificateList_AddReferences(subjectList);rvArray=get_array_from_list(subjectList,rvOpt,maximumOpt,arenaOpt);}PZ_Unlock(store->lock);returnrvArray;}/* Because only subject indexing is implemented, all other lookups require * full traversal (unfortunately, PLHashTable doesn't allow you to exit * early from the enumeration). The assumptions are that 1) lookups by * fields other than subject will be rare, and 2) the hash will not have * a large number of entries. These assumptions will be tested. * * XXX * For NSS 3.4, it is worth consideration to do all forms of indexing, * because the only crypto context is global and persistent. */structnickname_template_str{NSSUTF8*nickname;nssList*subjectList;};staticvoidmatch_nickname(constvoid*k,void*v,void*a){PRStatusnssrv;NSSCertificate*c;NSSUTF8*nickname;nssList*subjectList=(nssList*)v;structnickname_template_str*nt=(structnickname_template_str*)a;nssrv=nssList_GetArray(subjectList,(void**)&c,1);nickname=nssCertificate_GetNickname(c,NULL);if(nssrv==PR_SUCCESS&&nickname&&nssUTF8_Equal(nickname,nt->nickname,&nssrv)){nt->subjectList=subjectList;}nss_ZFreeIf(nickname);}/* * Find all cached certs with this label. */NSS_IMPLEMENTNSSCertificate**nssCertificateStore_FindCertificatesByNickname(nssCertificateStore*store,constNSSUTF8*nickname,NSSCertificate*rvOpt[],PRUint32maximumOpt,NSSArena*arenaOpt){NSSCertificate**rvArray=NULL;structnickname_template_strnt;nt.nickname=(char*)nickname;nt.subjectList=NULL;PZ_Lock(store->lock);nssHash_Iterate(store->subject,match_nickname,&nt);if(nt.subjectList){nssCertificateList_AddReferences(nt.subjectList);rvArray=get_array_from_list(nt.subjectList,rvOpt,maximumOpt,arenaOpt);}PZ_Unlock(store->lock);returnrvArray;}structemail_template_str{NSSASCII7*email;nssList*emailList;};staticvoidmatch_email(constvoid*k,void*v,void*a){PRStatusnssrv;NSSCertificate*c;nssList*subjectList=(nssList*)v;structemail_template_str*et=(structemail_template_str*)a;nssrv=nssList_GetArray(subjectList,(void**)&c,1);if(nssrv==PR_SUCCESS&&nssUTF8_Equal(c->email,et->email,&nssrv)){nssListIterator*iter=nssList_CreateIterator(subjectList);if(iter){for(c=(NSSCertificate*)nssListIterator_Start(iter);c!=(NSSCertificate*)NULL;c=(NSSCertificate*)nssListIterator_Next(iter)){nssList_Add(et->emailList,c);}nssListIterator_Finish(iter);nssListIterator_Destroy(iter);}}}/* * Find all cached certs with this email address. */NSS_IMPLEMENTNSSCertificate**nssCertificateStore_FindCertificatesByEmail(nssCertificateStore*store,NSSASCII7*email,NSSCertificate*rvOpt[],PRUint32maximumOpt,NSSArena*arenaOpt){NSSCertificate**rvArray=NULL;structemail_template_stret;et.email=email;et.emailList=nssList_Create(NULL,PR_FALSE);if(!et.emailList){returnNULL;}PZ_Lock(store->lock);nssHash_Iterate(store->subject,match_email,&et);if(et.emailList){/* get references before leaving the store's lock protection */nssCertificateList_AddReferences(et.emailList);}PZ_Unlock(store->lock);if(et.emailList){rvArray=get_array_from_list(et.emailList,rvOpt,maximumOpt,arenaOpt);nssList_Destroy(et.emailList);}returnrvArray;}/* Caller holds store->lock */staticNSSCertificate*nssCertStore_FindCertByIssuerAndSerialNumberLocked(nssCertificateStore*store,NSSDER*issuer,NSSDER*serial){certificate_hash_entry*entry;NSSCertificate*rvCert=NULL;NSSCertificateindex;index.issuer=*issuer;index.serial=*serial;entry=(certificate_hash_entry*)nssHash_Lookup(store->issuer_and_serial,&index);if(entry){rvCert=nssCertificate_AddRef(entry->cert);}returnrvCert;}NSS_IMPLEMENTNSSCertificate*nssCertificateStore_FindCertificateByIssuerAndSerialNumber(nssCertificateStore*store,NSSDER*issuer,NSSDER*serial){NSSCertificate*rvCert=NULL;PZ_Lock(store->lock);rvCert=nssCertStore_FindCertByIssuerAndSerialNumberLocked(store,issuer,serial);PZ_Unlock(store->lock);returnrvCert;}NSS_IMPLEMENTNSSCertificate*nssCertificateStore_FindCertificateByEncodedCertificate(nssCertificateStore*store,NSSDER*encoding){PRStatusnssrv=PR_FAILURE;NSSDERissuer,serial;NSSCertificate*rvCert=NULL;nssrv=nssPKIX509_GetIssuerAndSerialFromDER(encoding,&issuer,&serial);if(nssrv!=PR_SUCCESS){returnNULL;}rvCert=nssCertificateStore_FindCertificateByIssuerAndSerialNumber(store,&issuer,&serial);PORT_Free(issuer.data);PORT_Free(serial.data);returnrvCert;}NSS_EXTERNPRStatusnssCertificateStore_AddTrust(nssCertificateStore*store,NSSTrust*trust){NSSCertificate*cert;certificate_hash_entry*entry;cert=trust->certificate;PZ_Lock(store->lock);entry=(certificate_hash_entry*)nssHash_Lookup(store->issuer_and_serial,cert);if(entry){NSSTrust*newTrust=nssTrust_AddRef(trust);if(entry->trust){nssTrust_Destroy(entry->trust);}entry->trust=newTrust;}PZ_Unlock(store->lock);return(entry)?PR_SUCCESS:PR_FAILURE;}NSS_IMPLEMENTNSSTrust*nssCertificateStore_FindTrustForCertificate(nssCertificateStore*store,NSSCertificate*cert){certificate_hash_entry*entry;NSSTrust*rvTrust=NULL;PZ_Lock(store->lock);entry=(certificate_hash_entry*)nssHash_Lookup(store->issuer_and_serial,cert);if(entry&&entry->trust){rvTrust=nssTrust_AddRef(entry->trust);}PZ_Unlock(store->lock);returnrvTrust;}NSS_EXTERNPRStatusnssCertificateStore_AddSMIMEProfile(nssCertificateStore*store,nssSMIMEProfile*profile){NSSCertificate*cert;certificate_hash_entry*entry;cert=profile->certificate;PZ_Lock(store->lock);entry=(certificate_hash_entry*)nssHash_Lookup(store->issuer_and_serial,cert);if(entry){nssSMIMEProfile*newProfile=nssSMIMEProfile_AddRef(profile);if(entry->profile){nssSMIMEProfile_Destroy(entry->profile);}entry->profile=newProfile;}PZ_Unlock(store->lock);return(entry)?PR_SUCCESS:PR_FAILURE;}NSS_IMPLEMENTnssSMIMEProfile*nssCertificateStore_FindSMIMEProfileForCertificate(nssCertificateStore*store,NSSCertificate*cert){certificate_hash_entry*entry;nssSMIMEProfile*rvProfile=NULL;PZ_Lock(store->lock);entry=(certificate_hash_entry*)nssHash_Lookup(store->issuer_and_serial,cert);if(entry&&entry->profile){rvProfile=nssSMIMEProfile_AddRef(entry->profile);}PZ_Unlock(store->lock);returnrvProfile;}/* XXX this is also used by cache and should be somewhere else */staticPLHashNumbernss_certificate_hash(constvoid*key){unsignedinti;PLHashNumberh;NSSCertificate*c=(NSSCertificate*)key;h=0;for(i=0;i<c->issuer.size;i++)h=PR_ROTATE_LEFT32(h,4)^((unsignedchar*)c->issuer.data)[i];for(i=0;i<c->serial.size;i++)h=PR_ROTATE_LEFT32(h,4)^((unsignedchar*)c->serial.data)[i];returnh;}staticintnss_compare_certs(constvoid*v1,constvoid*v2){PRStatusignore;NSSCertificate*c1=(NSSCertificate*)v1;NSSCertificate*c2=(NSSCertificate*)v2;return(int)(nssItem_Equal(&c1->issuer,&c2->issuer,&ignore)&&nssItem_Equal(&c1->serial,&c2->serial,&ignore));}NSS_IMPLEMENTnssHash*nssHash_CreateCertificate(NSSArena*arenaOpt,PRUint32numBuckets){returnnssHash_Create(arenaOpt,numBuckets,nss_certificate_hash,nss_compare_certs,PL_CompareValues);}NSS_IMPLEMENTvoidnssCertificateStore_DumpStoreInfo(nssCertificateStore*store,void(*cert_dump_iter)(constvoid*,void*,void*),void*arg){PZ_Lock(store->lock);nssHash_Iterate(store->issuer_and_serial,cert_dump_iter,arg);PZ_Unlock(store->lock);}